package felix.rule.service.actors.chain;

import com.fasterxml.jackson.databind.ObjectMapper;
import felix.actor.core.TActorRef;
import felix.actor.core.msg.TActorMsg;
import felix.rule.api.RuleChainTransactionService;
import felix.rule.api.TContext;
import felix.rule.data.msg.EngineMsg;
import felix.rule.data.msg.EngineMsgMetaData;
import felix.rule.data.msg.RuleNode;
import felix.rule.js.script.ScriptEngine;
import felix.rule.js.script.ScriptEngineImpl;
import felix.rule.js.util.ListeningExecutor;
import felix.rule.service.actors.ActorSystemContext;
import felix.rule.service.queue.TQueueCallback;
import felix.rule.service.queue.TQueueMsgMetadata;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;

import java.util.Collections;
import java.util.Set;
import java.util.function.Consumer;

@Slf4j
class DefaultTContext implements TContext {

    public final static ObjectMapper mapper = new ObjectMapper();

    private final ActorSystemContext mainCtx;
    private final RuleNodeCtx nodeCtx;

    public DefaultTContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) {
        this.mainCtx = mainCtx;
        this.nodeCtx = nodeCtx;
    }

    @Override
    public void tellSuccess(EngineMsg msg) {
        tellNext(msg, Collections.singleton("SUCCESS"), null);
    }

    @Override
    public void tellNext(EngineMsg msg, String relationType) {
        tellNext(msg, Collections.singleton(relationType), null);
    }

    @Override
    public void tellNext(EngineMsg msg, Set<String> relationTypes) {
        tellNext(msg, relationTypes, null);
    }

    private void tellNext(EngineMsg msg, Set<String> relationTypes, Throwable th) {
        if (nodeCtx.getSelf().isDebugMode()) {
            relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getRuleId(), nodeCtx.getSelf().getId(), msg, relationType, th));
        }
        nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
    }

    @Override
    public void tellSelf(EngineMsg msg, long delayMs) {
        scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor());
    }

    @Override
    public void ack(EngineMsg engineMsg) {
        if (nodeCtx.getSelf().isDebugMode()) {
            mainCtx.persistDebugOutput(nodeCtx.getRuleId(), nodeCtx.getSelf().getId(), engineMsg, "ACK", null);
        }
        engineMsg.getCallback().onSuccess();
    }

    private void scheduleMsgWithDelay(TActorMsg msg, long delayInMs, TActorRef target) {
        mainCtx.scheduleMsgWithDelay(target, msg, delayInMs);
    }

    @Override
    public void tellFailure(EngineMsg msg, Throwable th) {
        if (nodeCtx.getSelf().isDebugMode()) {
            mainCtx.persistDebugOutput(nodeCtx.getRuleId(), nodeCtx.getSelf().getId(), msg, "FAILURE", th);
        }
        nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton("FAILURE"),
                msg, th != null ? th.getMessage() : null));
    }

    public void updateSelf(RuleNode self) {
        nodeCtx.setSelf(self);
    }

    @Override
    public EngineMsg newMsg(String queueName, String type, String originator, EngineMsgMetaData metaData, String data) {
        return EngineMsg.newMsg(queueName, type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
    }

    @Override
    public EngineMsg transformMsg(EngineMsg origMsg, String type, String originator, EngineMsgMetaData metaData, String data) {
        return EngineMsg.transformMsg(origMsg, type, originator, metaData, data);
    }

    @Override
    public String getSelfId() {
        return nodeCtx.getSelf().getId();
    }

    @Override
    public String getRuleId() {
        return nodeCtx.getRuleId();
    }

    @Override
    public ScriptEngine createJsScriptEngine(String script) {
        return new ScriptEngineImpl(mainCtx.getJsSandbox(), script);
    }

    @Override
    public ListeningExecutor getJsExecutor() {
        return mainCtx.getJsExecutor();
    }

    @Override
    public RuleChainTransactionService getRuleChainTransactionService() {
        return mainCtx.getRuleChainTransactionService();
    }

    @Override
    public ApplicationContext getSpringContext() {
        return mainCtx.getSpringContext();
    }

    private EngineMsgMetaData getActionMetaData(String ruleNodeId) {
        EngineMsgMetaData metaData = new EngineMsgMetaData();
        metaData.putValue("ruleNodeId", ruleNodeId);
        return metaData;
    }

    private class SimpleTQueueCallback implements TQueueCallback {
        private final Runnable onSuccess;
        private final Consumer<Throwable> onFailure;

        public SimpleTQueueCallback(Runnable onSuccess, Consumer<Throwable> onFailure) {
            this.onSuccess = onSuccess;
            this.onFailure = onFailure;
        }

        @Override
        public void onSuccess(TQueueMsgMetadata metadata) {
            if (onSuccess != null) {
                onSuccess.run();
            }
        }

        @Override
        public void onFailure(Throwable t) {
            if (onFailure != null) {
                onFailure.accept(t);
            } else {
                log.debug("[{}] Failed to put item into queue", nodeCtx.getRuleId(), t);
            }
        }
    }
}
